package parcomb

import testaux.Expr;
import testaux.FunctionList
import testaux.Program
import testaux.Function

import testaux.*;
import java.util.ArrayList

fun LPAR() = char('(');
fun RPAR() = char(')');
fun LSQ() = char('[');
fun RSQ() = char(']');
fun LC() = char('{');
fun RC() = char('}');
fun LT() = char('<');
fun GT() = char('>');
fun ASSIGN() = char('=');
fun SEMICOLON() = char(';');
fun COMMA() = char(',');
fun IF() = string("IF".toCharList());
fun FUNCTION() = string("DEF".toCharList());
fun PASS() = string("PASS".toCharList());
fun RET() = string("RET".toCharList());
fun whitespace() = sat({(it: Char) -> ((it == ' ') || (it == '\t') || (it == '\r') || (it == '\n'))})
fun spaces() = force(many(whitespace()))

fun force<A, B>(p: (List<A>) -> Pair<Pair<List<B>, List<A>>?, Error?>) = {(s: List<A>) ->
    val ans = p(s);
    empty<A, B>(getStream(ans), getError(ans));
}

fun ident() =
        bind(letter(),          {(x: Char)        ->
        bind(many(alphanum()),  {(xs: List<Char>) ->
            result(x cons xs)
        })})

fun nat2ast()   = apply(nat(),   {(x: Int)        -> Expr(Expr.INTCONST, Integer(x))})
fun ident2ast() = apply(ident(), {(x: List<Char>) -> Expr(Expr.IDENT, x.toString())})
fun invocation2ast() = apply(invocation(), {(x: Invocation) -> Expr(Expr.INVOCATION, x)})

fun invid2ast() =
        bind(ident(),                {(i: List<Char>) ->
        bind1<Char, ExprList?, Expr>(bind(lstrip(LPAR()),       {
              bind1(lstrip(expr_list()), {(ec: ExprList?) ->
              bind(lstrip(RPAR()),       {
                  result(ec);})})}), {(ec: ExprList?) ->
              result(if (ec == null) Expr(Expr.IDENT, i.toString()) else Expr(Expr.INVOCATION, Invocation(i.toString(), ec)))
        })})

fun lstrip<B>(p: (List<Char>) -> Pair<Pair<List<B>, List<Char>>?, Error?>) =
        bind1(spaces(), {p})

fun program() =
        bind(lstrip(ident()),       {(name: List<Char>) ->
        bind(lstrip(SEMICOLON()),   {(sc: Char)         ->
        bind(lstrip(func_list()),   {(fc: FunctionList) ->
            result(Program(name.toString(), fc));
        })})})

fun func_list(): (List<Char>) -> Pair<Pair<List<FunctionList>, List<Char>>?, Error?>  =
        bind(func_decl(),           {(f: Function)       ->
        bind1(lstrip(func_list()),  {(fc: FunctionList?) ->
            result(FunctionList(f, fc))
        })})

fun func_decl() =
        bind(FUNCTION(),            {(_: List<Char>)   ->
        bind(lstrip(ident()),       {(i: List<Char>)   ->
        bind(lstrip(LPAR()),        {(_: Char)         ->
        bind1(lstrip(ident_list()), {(ac: StringList?) ->
        bind(lstrip(RPAR()),        {(_: Char)         ->
        bind(lstrip(block()),       {(b: Block)        ->
            result(Function(i.toString(), ac, b))
        })})})})})})

fun block() =
        bind(LC(),              {(_: Char) ->
        bind(lstrip(st_list()), {(sc: StatementList) ->
        bind(lstrip(RC()),      {(_: Char) ->
            result(Block(sc))
        })})})

fun ident_list() : (List<Char>) -> Pair<Pair<List<StringList>, List<Char>>?, Error?> =
        bind(ident(),           {(st: List<Char>) ->
        bind1(lstrip(COMMA()),  {(_: Char?) ->
            if (_ == null)
                result(StringList(st.toString()))
            else
                bind(lstrip(ident_list()), {(sc: StringList?) ->
                    result(StringList(st.toString(), sc))
                })
        })})

fun st_list() : (List<Char>) -> Pair<Pair<List<StatementList>, List<Char>>?, Error?> =
        bind(statement(),           {(st: Statement) ->
        bind1(lstrip(SEMICOLON()),  {(_: Char?) ->
            if (_ == null)
                result(StatementList(st))
            else
                bind(lstrip(st_list()), {(sc: StatementList?) ->
                    result(StatementList(st, sc))
                })
        })})

fun statement() =
   alt(apply(assignment(),  {(a: Assignment)  -> Statement(Sym.ASSIGN, a)}),
   alt(apply(block(),       {(b: Block)       -> Statement(Sym.BLOCK, b)}),
   alt(apply(if_st(),       {(i: IfStatement) -> Statement(Sym.IF, i)}),
   alt(apply(ret(),         {(r: Return)      -> Statement(Sym.RET, r)}),
       apply(PASS(),        {(_: List<Char>)  -> Statement(Sym.PASS, null)})
   ))))

fun assignment() =
    bind(ident(),           {(i: List<Char>) ->
    bind(lstrip(ASSIGN()),  {
    bind(lstrip(expr()),    {(e: Expr)       ->
        result(Assignment(i.toString(), e));
    })})})

fun if_st() =
    bind(IF(),                  {
    bind(lstrip(condition()),   {(c: Condition)  ->
    bind(lstrip(block()),       {(b1: Block)     ->
    bind(lstrip(block()),       {(b2: Block)     ->
        result(IfStatement(c, b1, b2))
    })})})})

fun condition() =
    bind(LSQ(),             {(lsq: Char) ->
    bind(lstrip(expr()),    {(e1: Expr)  ->
    lstrip(alt(
            bind(LT(),{
            bind(lstrip(expr()),    {(e2: Expr)  ->
            bind(lstrip(RSQ()),     {(rsq: Char) ->
                result(Condition(Sym.LT, e1, e2))
            })})}),
            bind(GT(), {
            bind(lstrip(expr()),    {(e2: Expr)  ->
            bind(lstrip(RSQ()),     {(rsq: Char) ->
                result(Condition(Sym.GT, e1, e2))
            })})})
    ))})})

fun invocation() =
        bind(ident(),               {(i: List<Char>) ->
        bind(lstrip(LPAR()),        {
        bind1(lstrip(expr_list()),  {(ec: ExprList?) ->
        bind(lstrip(RPAR()),        {
            result(Invocation(i.toString(), ec))
        })})})})

fun expr_list(): (List<Char>) -> Pair<Pair<List<ExprList>, List<Char>>?, Error?> =
        bind(expr(),           {(e: Expr)  ->
        bind1(lstrip(COMMA()), {(_: Char?) ->
            if (_ == null)
                result(ExprList(e))
            else
                bind(lstrip(expr_list()), {(ec: ExprList) ->
                    result(ExprList(e, ec))
                })
        })})

fun ret() =
        bind(RET(),          {(_: List<Char>) ->
        bind(lstrip(expr()), {(e: Expr)       ->
            result(Return(e))
        })})

fun getOp(op: Char) =
        when (op) {
            '+'  -> {(x: Expr, y: Expr) -> Expr(Expr.ADD, null, asList(x, y)) }
            '-'  -> {(x: Expr, y: Expr) -> Expr(Expr.MIN, null, asList(x, y)) }
            '*'  -> {(x: Expr, y: Expr) -> Expr(Expr.MUL, null, asList(x, y)) }
            else -> {(x: Expr, y: Expr) -> Expr(Expr.error, null) }
        }

fun addop(): (List<Char>) -> Pair<Pair<List<((Expr, Expr) -> Expr)>, List<Char>>?, Error?> =
        alt(apply(char('+'), {(c: Char) -> getOp(c) }),
            apply(char('-'), {(c: Char) -> getOp(c) })
        )

fun multop(): (List<Char>) -> Pair<Pair<List<((Expr, Expr) -> Expr)>, List<Char>>?, Error?> =
        apply(char('*'), {(c: Char) -> getOp(c) })

fun expr() = chainl1<Char, Expr>(lstrip(term()), lstrip(addop()))
fun term() = chainl1<Char, Expr>(lstrip(factor()), lstrip(multop()))
fun factor(): (List<Char>) -> Pair<Pair<List<Expr>, List<Char>>?, Error?> =
        alt(nat2ast(),
        alt(invid2ast(),
            bracket(LPAR(), lstrip({(s) -> expr()(s) }), lstrip(RPAR()))))